package com.rainbow.common.handler.error;

import com.google.common.collect.Maps;
import com.rainbow.common.core.constant.Constant;
import com.rainbow.common.core.entity.Result;
import com.rainbow.common.core.error.GlobalErrorCode;
import com.rainbow.common.core.error.GlobalException;
import com.rainbow.common.core.toolkit.ContextHolder;
import com.rainbow.common.core.toolkit.ErrorPrintHandle;
import com.rainbow.common.core.toolkit.MessageI18nUtil;
import feign.FeignException;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.skywalking.apm.toolkit.trace.Trace;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.HttpStatus;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 全局异常处理
 * @author 傅为地
 */
@ControllerAdvice(annotations = {RestController.class})
@ConditionalOnProperty(prefix = "rainbow.common.handler.error", name = "enabled", havingValue = "true", matchIfMissing = true)
@Slf4j
public class GlobalErrorHandler {


    /**
     * 参数异常处理
     * @param ex
     * @return
     */
    @ExceptionHandler({IllegalArgumentException.class})
    @ResponseBody
    @Trace
    public Object validExceptionHandler(IllegalArgumentException ex) {
        log.error(ErrorPrintHandle.getErrorStackDetail(ex));
        ContextHolder.getInstance().remove(Constant.CURRENT_LOGIN_USER);
        return Result.error(HttpStatus.BAD_REQUEST.value(), GlobalErrorCode.PARAMETER_VERIFICATION_ERROR.message(),ex.getMessage());
    }

    /**
     * 缺少参数验证异常
     * @param ex
     * @return
     */
    @ExceptionHandler({MissingServletRequestParameterException.class})
    @ResponseBody
    @Trace
    public Object validExceptionHandler(MissingServletRequestParameterException ex) {
        log.error(ErrorPrintHandle.getErrorStackDetail(ex));
        ContextHolder.getInstance().remove(Constant.CURRENT_LOGIN_USER);
        return Result.error(HttpStatus.BAD_REQUEST.value(), GlobalErrorCode.PARAMETER_VERIFICATION_ERROR.message(),"Missing Parameter:["+ex.getParameterName()+"]");
    }

    /**
     * post验证参数异常
     * @param ex
     * @return
     */
    @ExceptionHandler({MethodArgumentNotValidException.class})
    @ResponseBody
    @Trace
    public Object validExceptionHandler(MethodArgumentNotValidException ex) {
        log.error(ErrorPrintHandle.getErrorStackDetail(ex));
        ContextHolder.getInstance().remove(Constant.CURRENT_LOGIN_USER);
        BindingResult bindingResult = ((MethodArgumentNotValidException) ex).getBindingResult();
        if (!ObjectUtils.isEmpty(bindingResult) && bindingResult.hasErrors()) {
            return Result.error(HttpStatus.BAD_REQUEST.value(), GlobalErrorCode.PARAMETER_VERIFICATION_ERROR.message(), ErrorPrintHandle.validErrorResult(bindingResult));
        } else {
            return Result.error(HttpStatus.BAD_REQUEST.value(), GlobalErrorCode.PARAMETER_VERIFICATION_ERROR.message());
        }
    }

    /**
     * 单参数验证异常
     * @param ex
     * @return
     */
    @ExceptionHandler({ConstraintViolationException.class})
    @ResponseBody
    @Trace
    public Object validExceptionHandler(ConstraintViolationException ex) {
        log.error(ErrorPrintHandle.getErrorStackDetail(ex));
        ContextHolder.getInstance().remove(Constant.CURRENT_LOGIN_USER);
        ConstraintViolationException exception = (ConstraintViolationException) ex;
        List<Map<String, String>> errorList=new ArrayList<Map<String, String>>();
        for (ConstraintViolation constraint : exception.getConstraintViolations()) {
            Map<String, String> map=Maps.newHashMap();
            map.put("field", Objects.toString(constraint.getInvalidValue()));
            map.put("error", MessageI18nUtil.getMessage(constraint.getMessage()));
            errorList.add(map);
        }
        //针对field字段不为空，才做排序，空值需要单独处理
        ErrorPrintHandle.sortList(errorList);
        return Result.error(HttpStatus.BAD_REQUEST.value(), GlobalErrorCode.PARAMETER_VERIFICATION_ERROR.message(), errorList);
    }

    /**
     * get验证参数异常
     * @param ex
     * @return
     */
    @ExceptionHandler({BindException.class})
    @ResponseBody
    @Trace
    public Object validExceptionHandler(BindException ex) {
        log.error(ErrorPrintHandle.getErrorStackDetail(ex));
        ContextHolder.getInstance().remove(Constant.CURRENT_LOGIN_USER);
        BindingResult bindingResult = ((BindException) ex).getBindingResult();
        if (!ObjectUtils.isEmpty(bindingResult) && bindingResult.hasErrors()) {
            return Result.error(HttpStatus.BAD_REQUEST.value(), GlobalErrorCode.PARAMETER_VERIFICATION_ERROR.message(), ErrorPrintHandle.validErrorResult(bindingResult));
        } else {
            return Result.error(HttpStatus.BAD_REQUEST.value(), GlobalErrorCode.PARAMETER_VERIFICATION_ERROR.message());
        }
    }


    /**
     * feign远程调用
     * 如果feign调用的返回参数类型是Result
     * 如果feign调用的返回参数不是Result,最好原类型解析出来返回出去
     * @param ex
     * @return
     */
    @ExceptionHandler({FeignException.class})
    @ResponseBody
    @Trace
    public Object feignExceptionHandler(FeignException ex){
        log.error(ErrorPrintHandle.getErrorStackDetail(ex));
        ContextHolder.getInstance().remove(Constant.CURRENT_LOGIN_USER);
        return Result.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), GlobalErrorCode.INTERNAL_SERVER_ERROR.message());
    }

    /**
     * 自定义异常出来
     * @param ex
     * @return
     */
    @ExceptionHandler({GlobalException.class})
    @ResponseBody
    @Trace
    @SneakyThrows
    public Object globalExceptionHandler(GlobalException ex){
        log.error(ErrorPrintHandle.getErrorStackDetail(ex));
        ContextHolder.getInstance().remove(Constant.CURRENT_LOGIN_USER);
        return Result.error(ex.getCode(), ex.getMessage(),ex.getData());
    }

    /**
     * 针对降级异常，不要被全局异常捕获
     * 服务出现异常，需要进入降级的服务
     * @param ex
     * @return
     */
    @ExceptionHandler({Exception.class})
    @ResponseBody
    @Trace
    @SneakyThrows
    public Object defaultExceptionHandler(Exception ex){
        log.error(ErrorPrintHandle.getErrorStackDetail(ex));
        ContextHolder.getInstance().remove(Constant.CURRENT_LOGIN_USER);
        return Result.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), GlobalErrorCode.INTERNAL_SERVER_ERROR.message());
    }


}
